package de.hs.rm.UJAdapter.event.impl.jmx;

import java.io.IOException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Set;

import javax.management.AttributeChangeNotification;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerDelegateMBean;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.upnp.UPnPDevice;
import org.osgi.service.upnp.UPnPEventListener;
import org.osgi.service.upnp.UPnPService;

import de.hs.rm.UJAdapter.factory.UJDeviceFactory;
import de.hs.rm.UJAdapter.factory.UJServiceFactory;
import de.hs.rm.UJAdapter.impl.UJDevice;
import de.hs.rm.UJAdapter.impl.UJService;
import de.hs.rm.UJAdapter.util.StringGenerationUtil;

public class UJRegisterNotificationListener implements NotificationListener {
		
		private Dictionary subDeviceRegistrations = null;
		private BundleContext context = null;
		private MBeanServerDelegateMBean delegateProxyMBean;
		private MBeanServerConnection mBeanServerConnection;
		private String rootDeviceUDN;
		
		private final int DEVICE_DOES_NOT_EXIST = -1;

		@Override
		public void handleNotification(Notification notification,
	            Object handback)
		{
			if (!(notification instanceof MBeanServerNotification)) {
	             System.out.println("Ignored notification of class " + notification.getClass().getName());
	             return;
			}
			MBeanServerNotification mbsn = (MBeanServerNotification) notification;
			ObjectName objName = mbsn.getMBeanName();
			
			Enumeration<String> deviceUDNs = subDeviceRegistrations.keys();
			UJDevice[] ujDevices = new UJDevice[subDeviceRegistrations.size()];
			String[] ujDevUDNs = new String[subDeviceRegistrations.size()];
			ServiceRegistration serviceRegistration = null;
			int i = 0;
			while (deviceUDNs.hasMoreElements())
			{
				ujDevUDNs[i] = deviceUDNs.nextElement();
				serviceRegistration  = (ServiceRegistration)subDeviceRegistrations.get(ujDevUDNs[i]);
				ujDevices[i] = (UJDevice) context.getService(serviceRegistration.getReference());
				i++;
				//unregisterDevice(serviceRegistration, ujDevUDNs[i]);
				//registerDevice(dev, deviceUDN);
			}

			if (notification.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION))
				handleRegistration(objName, ujDevices, ujDevUDNs);
			else if (notification.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION))
				handleUnregistration(objName, ujDevices, ujDevUDNs);
        
		}
		
		//handle registration notification
		private void handleRegistration(ObjectName objName, UJDevice[] ujDevices, String[] ujDevUDNs)
		{
			//String domain = objName.getDomain();
			String changedDevUDN = StringGenerationUtil.getUDNfromDomainName(objName.getDomain());
			ServiceRegistration serviceRegistration = null;
			//UJDevice changedDev = null;
			int changedDevIndex = DEVICE_DOES_NOT_EXIST;
			MBeanInfo mBeanInfo = null;
			try {
				mBeanInfo = mBeanServerConnection.getMBeanInfo(objName);	
				//find subDevice (representing the MBean domain) if it already exists
				for(int i = 0; i < ujDevUDNs.length; i++)
				{
					if(changedDevUDN.equals(ujDevUDNs[i]))
							changedDevIndex = i;
				}
				
				//if domain already exists
				if (changedDevIndex != DEVICE_DOES_NOT_EXIST)
				{
					//update device services (add new MBean)
					serviceRegistration  = (ServiceRegistration)subDeviceRegistrations.get(ujDevUDNs[changedDevIndex]);
					ujDevices[changedDevIndex] = (UJDevice) context.getService(serviceRegistration.getReference());
					UPnPService services[] = ujDevices[changedDevIndex].getServices();
		    		UPnPService newServices[] = new UPnPService[services.length+1];
		    		for(int i = 0; i < services.length; i++)
		    		{
		    			newServices[i] = services[i];	    			
		    		}
		    		newServices[services.length] = UJServiceFactory.createService(ujDevices[changedDevIndex], delegateProxyMBean, mBeanInfo, mBeanServerConnection, objName);
		    		ujDevices[changedDevIndex].setServices(newServices);
		    		
		    		//register updated device
		    		for(int i = 0; i < ujDevUDNs.length; i++)
		    			unregisterDevice(ujDevUDNs[i]);
		    		
		    		for(int i = 0; i < ujDevUDNs.length; i++)
		    			registerDevice(ujDevices[i],ujDevUDNs[i]);
		    		
		    		//register updated device
		    		//registerDevice(changedDev, deviceUDN);
				}
				else //device doesn't exist, must create new device for new domain
				{
					//create new device
					UJDevice newSubDevice =
	            			UJDeviceFactory.createSubDevice(context, delegateProxyMBean, changedDevUDN , rootDeviceUDN);
					UPnPService newServices[] = new UPnPService[1];
					newServices[0] = UJServiceFactory.createService(newSubDevice, delegateProxyMBean, mBeanInfo, mBeanServerConnection, objName);
					newSubDevice.setServices(newServices);
					registerDevice(newSubDevice, changedDevUDN);
					updateRootDevice();
				}			
				
			} catch (InstanceNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IntrospectionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ReflectionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		//handle unregistration notification
		private void handleUnregistration(ObjectName objName, UJDevice[] ujDevices, String[] ujDevUDNs)
		{
			String changedDevUDN = StringGenerationUtil.getUDNfromDomainName(objName.getDomain());
			String serviceID = StringGenerationUtil.getServiceIDfromObjName(objName);
			ServiceRegistration serviceRegistration = null;
			int changedDevIndex = DEVICE_DOES_NOT_EXIST;
			
			for(int i = 0; i < ujDevUDNs.length; i++)
			{
				if(changedDevUDN.equals(ujDevUDNs[i]))
						changedDevIndex = i;
			}
			
			//get changed device
			serviceRegistration  = (ServiceRegistration)subDeviceRegistrations.get(ujDevUDNs[changedDevIndex]);
			ujDevices[changedDevIndex] = (UJDevice) context.getService(serviceRegistration.getReference());
				    	
	    	//if device has more than one service (= representation of MBean) left, delete it, else delete whole domain
	    	if(ujDevices[changedDevIndex].getServices().length > 1)
	    	{
	    		//update device services (remove unregistered MBean)
	    		UPnPService[] services = ujDevices[changedDevIndex].getServices();
	    		UPnPService newServices[] = new UPnPService[services.length-1];
	    		int i = 0;
	    		for(UPnPService service : services)
	    		{
	    			if(!service.getId().equals(serviceID))
	    			{
	    				newServices[i] = service;
	    				i++;
	    			}
//	    			else
//	    			{
//	    				//remove notification listener if any exist
//	    				UJService ujService = (UJService)service;
//	    				NotificationListener listener = ujService.getNotificationListener();
//	    				if(listener != null)
//	    				{	    					
//							try {
//								mBeanServerConnection.removeNotificationListener(objName,listener, null, null);
//							} catch (InstanceNotFoundException e) {
//								e.printStackTrace();
//							}
//							catch (ListenerNotFoundException e) {
//								e.printStackTrace();
//							}
//							catch (IOException e) {
//								e.printStackTrace();
//							}
//	    				}
//	    			}
	    		}
	    		ujDevices[changedDevIndex].setServices(newServices);
	    		
	    		//register updated device
	    		for(int j = 0; j < ujDevUDNs.length; j++)
	    			unregisterDevice(ujDevUDNs[j]);
	    		
	    		for(int j = 0; j < ujDevUDNs.length; j++)
	    			registerDevice(ujDevices[j],ujDevUDNs[j]);	    		
	    	}
	    	//delete device(=domain) if only one service exists
	    	else
	    	{
	    		unregisterDevice(changedDevUDN);	    	
	    		updateRootDevice();
	    	}
	    	
		}
		
		//register device
		private void registerDevice(UJDevice device, String deviceUDN)
		{
			Dictionary dictSub = device.getDescriptions(null);
			ServiceRegistration serviceRegistration = context.registerService(
    				UPnPDevice.class.getName(),
    				device,
    				dictSub
    		);
            if(serviceRegistration != null)
            	subDeviceRegistrations.put(deviceUDN, serviceRegistration);
		}
		
		//unregister device by UDN
		private void unregisterDevice(String deviceUDN)
		{
			ServiceRegistration serviceRegistration = (ServiceRegistration)subDeviceRegistrations.get(deviceUDN); 
			subDeviceRegistrations.remove(deviceUDN);
			serviceRegistration.unregister();
			
		}
		
		private void addUDNtoRootDevice(String newUDN)
		{
			ServiceRegistration serviceRegistration  = (ServiceRegistration)subDeviceRegistrations.get(rootDeviceUDN);
	    	UJDevice rootDev = (UJDevice) context.getService(serviceRegistration.getReference());
	    	
	    	unregisterDevice(rootDeviceUDN);
	    	
	    	Dictionary props = rootDev.getDescriptions("");
	    	String[] oldChildrenUDN = (String[])props.get(UPnPDevice.CHILDREN_UDN);
	    	String[] newChildrenUDN = new String[oldChildrenUDN.length + 1];
	    	for(int i = 0; i < oldChildrenUDN.length; i++)
	    		newChildrenUDN[i] = oldChildrenUDN[i];
	    	newChildrenUDN[oldChildrenUDN.length] = newUDN;
	    	props.put(UPnPDevice.CHILDREN_UDN, newChildrenUDN);
			rootDev.setDescriptions(props);
	    	
			registerDevice(rootDev, rootDeviceUDN);
			
		}
		
		//update root device  (childrenUDNs) if subdevice is created or deleted
		private void updateRootDevice()
		{			
			//update children UDN
			ServiceRegistration serviceRegistration  = (ServiceRegistration)subDeviceRegistrations.get(rootDeviceUDN);
	    	UJDevice rootDev = (UJDevice) context.getService(serviceRegistration.getReference());
	    	Dictionary props = rootDev.getDescriptions("");
	    	
	    	String[] childrenUDN = new String[subDeviceRegistrations.size()-1];
	    	Enumeration<String> deviceUDNs = subDeviceRegistrations.keys();
	    	
	    	String deviceUDN = "";
	    	int i = 0;
			while (deviceUDNs.hasMoreElements())
			{
				deviceUDN = deviceUDNs.nextElement();
				if(!deviceUDN.equals(rootDeviceUDN))
				{
					childrenUDN[i] = deviceUDN;
					i++;
				}	    		
			}
			
			props.put(UPnPDevice.CHILDREN_UDN, childrenUDN);
			rootDev.setDescriptions(props);
			
			//register updated root device
	    	unregisterDevice(rootDeviceUDN);
	    	registerDevice(rootDev, rootDeviceUDN);
		}
		
		public void setListenerProps(BundleContext context, Dictionary subDeviceRegistrations, MBeanServerDelegateMBean delegateProxyMBean, MBeanServerConnection mBeanServerConnection, String rootDeviceUDN)
		{
		    this.context = context;
		    this.subDeviceRegistrations = subDeviceRegistrations;
		    this.delegateProxyMBean = delegateProxyMBean;
		    this.mBeanServerConnection = mBeanServerConnection;
		    this.rootDeviceUDN = rootDeviceUDN;
		  }

}
